package cz.zcu.sefler.mois.business;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import net.htmlparser.jericho.HTMLElementName;
import net.htmlparser.jericho.MasonTagTypes;
import net.htmlparser.jericho.MicrosoftTagTypes;
import net.htmlparser.jericho.PHPTagTypes;
import net.htmlparser.jericho.Segment;
import net.htmlparser.jericho.Source;
import net.htmlparser.jericho.StartTag;
import net.htmlparser.jericho.TextExtractor;
import spiaotools.SentParDetector;
import cz.zcu.sefler.mois.data.MonitorDAO;
import cz.zcu.sefler.mois.data.model.Monitor;
import cz.zcu.sefler.mois.data.model.Sentence;

/**
 * Abstraktni trida, kterou musi zdedit kazda trida, ktera ma zpracovavat
 * specificke rozhovory. Obsahuje metody pro rozdeleni textu na jednotlive vety
 * a jejich klasifikaci.
 * 
 * @author Vojtech Sefler
 * 
 */
@SuppressWarnings("deprecation")
public abstract class GeneralMonitorThread extends Thread {
  /* Rozhovor, ktery bude instance teto tridy zpracovavat */
  private Monitor        monitor;

  private MonitorDAO     monitorDAO;

  /* Text rozhovoru. */
  private String         text;

  /* Prazdny retezec. */
  private final String   EMPTY_STRING            = "";

  /* Velka pismena s ceskou diakritikou. */
  private final String[] CZECH_DIACRITICAL_MARKS = new String[] { "Á", "Č", "Ď", "É", "Ě", "Í", "Ň", "Ó", "Ř", "Š", "Ť", "Ú", "Ý", "Ž" };

  /* Znamenka konce vet. */
  private final String[] SENTENCE_END_MARKS      = new String[] { "!", "?", "." };

  /* Tazaci slova, kterymi zacina veta doplnovaci. */
  private final String[] QUESTION_WORDS          = new String[] { "Co ", "Jaký ", "Jaká ", "Jaké ", "Který ", "Která ", "Které ", "Kde ",
      "Kam ", "Kudy ", "Kdy ", "Kdo ", "Koho ", "Komu ", "Čí ", "Proč ", "Jak ", "Kolik ", "Kolikrát ", "Pročpak ", "V čem", "A co ",
      "A jaký ", "A jaká ", "A jaké ", "A který ", "A která ", "A které ", "A kde ", "A kam ", "A kudy ", "A kdy ", "A kdo ", "A koho ",
      "A komu ", "A čí ", "A proč ", "A jak ", "A kolik ", "A kolikrát ", "A pročpak ", "A v čem", ", co ", ", jaký ", ", jaká ",
      ", jaké ", ", který ", ", která ", ", které ", ", kde ", ", kam ", ", kudy ", ", kdy ", ", kdo ", ", koho ", ", komu ", ", čí ",
      ", proč ", ", jak ", ", kolik ", ", kolikrát ", ", pročpak ", ", v čem", ", a co ", ", a jaký ", ", a jaká ", ", a jaké ",
      ", a který ", ", a která ", ", a které ", ", a kde ", ", a kam ", ", a kudy ", ", a kdy ", ", a kdo ", ", a koho ", ", a komu ",
      ", a čí ", ", a proč ", ", a jak ", ", a kolik ", ", a kolikrát ", ", a pročpak ", ", a v čem" };

  /* Popisky typu vet. */
  private final String   SENT_OZNAMOVACI         = "oznamovaci";
  private final String   SENT_ROZKAZOVACI        = "rozkazovaci";
  private final String   SENT_DOPLNOVACI         = "doplnovaci";
  private final String   SENT_ZJISTOVACI         = "zjistovaci";

  public GeneralMonitorThread(Monitor monitor, String dbMachine, String dbDb, String dbUser, String dbPass) {
    super();
    this.monitor = monitor;
    this.monitorDAO = new MonitorDAO(dbMachine, dbDb, dbUser, dbPass);
  }

  /**
   * Tato metoda rozdeli text na vety. Kazda veta je na jedne radce. Odstranuje
   * tez primo rec (uvozovky). Bere do uvahy i primou rec v kombinaci s ceskou
   * diakritikou.
   * 
   * @param rawText Text, ktery ma byt rozdelen na vety.
   * @return Zpracovany text.
   */
  private String getSentParsedText(String rawText) {
    try {
      /* Text nejprve rozdeli na jednotlive vety knihovna spiaotools. */
      SentParDetector spd = new SentParDetector();
      spd.markTitle(false);
      /*
       * Prvni parametr rika, ze vety budou oddeleny novou radkou. Dalsi
       * identifikatory nebudou pridany (napr. cislo vety).
       */
      rawText = spd.markupRawText(2, rawText);

      /*
       * Detekce ceskych diakritickych znamenek na zacatku vety a prime reci s
       * diakritikou.
       */
      for (int i = 0; i < CZECH_DIACRITICAL_MARKS.length; i++) {
        /*
         * Odstrani primou rec v pripadech, kdy po ukoncovacich uvozovkach
         * nasleduje dalsi veta, napr.: 
         * pocatecni stav: 
         *   "To ne!" Šeptala mi napoveda.
         * po uprave:
         *   "To ne! Šeptala mi napoveda.
         */
        rawText = rawText.replace("“ " + CZECH_DIACRITICAL_MARKS[i], "\n" + CZECH_DIACRITICAL_MARKS[i]);
        rawText = rawText.replace("\" " + CZECH_DIACRITICAL_MARKS[i], "\n" + CZECH_DIACRITICAL_MARKS[i]);
        rawText = rawText.replace(" „" + CZECH_DIACRITICAL_MARKS[i], "\n" + CZECH_DIACRITICAL_MARKS[i]);

        /*
         * Rozdeleni dvou vet oddelenych dvojteckou, kde druha veta zacina pismenem s ceskou diakritikou.
         */

        rawText = rawText.replace(": " + CZECH_DIACRITICAL_MARKS[i], ":\n" + CZECH_DIACRITICAL_MARKS[i]);
        
        /*
         * Detekuje začátek nové věty, která začíná písmenem s diakritikou. Toto
         * knihovna spiaotools nedokaze.
         */
        for (int j = 0; j < SENTENCE_END_MARKS.length; j++) {
          rawText = rawText.replace(SENTENCE_END_MARKS[j] + " " + CZECH_DIACRITICAL_MARKS[i], SENTENCE_END_MARKS[j] + "\n"
              + CZECH_DIACRITICAL_MARKS[i]);
        }
      }

      /* Detekce a odstraneni prime reci na konci ci na zacatku vety. */
      for (int i = (int) 'A'; i <= (int) 'Z'; i++) {
        String capsChar = Character.toString((char) i);
        /*
         * pocatecni stav:
         *   A Patrik kricel. „Ja to nebyl!"
         * po uprave:
         *   A Patrik kricel. Ja to nebyl!"
         */
        rawText = rawText.replace(" „" + capsChar, "\n" + capsChar);

        /*
         * Tyto dve pravidla jsou obdobou odstraneni prime reci s ceskou
         * diakritikou, pouze se aplikuji na vety, ktere zacinaji velkymi
         * pismeny bez diakritiky.
         */
        rawText = rawText.replace("“ " + capsChar, "\n" + capsChar);
        rawText = rawText.replace("\" " + capsChar, "\n" + capsChar);
        
        /*
         * Rozdeleni dvou vet oddelenych dvojteckou.
         */
        rawText = rawText.replace(": " + capsChar, ":\n" + capsChar);

      }
    } catch (Exception e) {
      /*
       * Pokud rozhovor neobsahuje zadny text, vratime radeji prazdny retezec,
       * nez null.
       */
      return EMPTY_STRING;
    }

    return rawText;
  }

  /**
   * Metoda vrati text obsazeny v jednotlivych HTML segmentu (napr. jen cast
   * cele stranky), krome odkazu.
   * 
   * @param source
   *          Zdrojovy segment.
   * @return Textova reprezentace segmentu.
   */
  protected String sourceSegmentToText(Segment source) {
    TextExtractor textExtractor = new TextExtractor(source) {
      public boolean excludeElement(StartTag startTag) {
        return startTag.getName() == HTMLElementName.A;
      }
    };

    /* Atributy znacek ignorujeme. */
    textExtractor.setIncludeAttributes(false);

    return textExtractor.toString();
  }

  /**
   * Metoda nacte adresu stranky, zpracuje jednotlive HTML elementy a vrati text
   * na strance obsazeny.
   * 
   * @param url
   *          Adresa stranky.
   * @return Textova reprezentace stranky.
   */
  private String getRawText(String url) {
    /* Nastaveni Jericho parseru. */
    MicrosoftTagTypes.register();
    PHPTagTypes.register();
    PHPTagTypes.PHP_SHORT.deregister();
    MasonTagTypes.register();

    Source source;
    try {
      source = new Source(new URL(url));

      /* Parsovani stranky na jednotlive elementy. */
      source.fullSequentialParse();

      String returnString = sourceSegmentToText(source).toString();
      if (returnString == null) {
        /*
         * Pokud segment stranky neobsahuje zadny text, vratime radeji prazdny
         * retezec, nez null.
         */
        return EMPTY_STRING;
      }
      return returnString;

    } catch (MalformedURLException e) {
      e.printStackTrace();
      return EMPTY_STRING;

    } catch (IOException e) {
      e.printStackTrace();
      return EMPTY_STRING;
    }
  }

  /**
   * Metoda ulozi text do listu. Kazdy objekt listu odpovida samostatne radce
   * textu.
   * 
   * @param text
   *          Text, ktery ma byt rozdelen.
   * @param list
   *          List vet.
   */
  protected void textLinesToList(String text, ArrayList<Sentence> list) {

    /* Text rozdelime dle konce radek. */
    String[] pole = text.split("\n");
    for (String line : pole) {
      /* Vlozeni nove radky do listu. */
      list.add(new Sentence(line, null));
    }
  }

  /**
   * Pracovni metoda vlakna. Ziska text ze stranky, rozdeli jej na vety a ty
   * klasifikuje. Nakonec vysledny rozhovor ulozi do databaze
   */
  public void run() {
    try {

      /* Ziskani cisteho textu ze stranky. */
      text = getRawText(monitor.getUrl());
      
      /* Rozdeleni textu na vety. Kazda veta na samostatne radce. */
      text = getSentParsedText(text);
      
      /* Vety jsou vlozeny do listu. */
      textLinesToList(text, monitor.getListSentences());
      
      /* Klasifikace vet. */
      categorizeSentences(monitor.getListSentences());

      /* Ulozeni rozhovoru do databaze. */
      monitorDAO.storeMonitor(this.monitor);

    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  /**
   * Metoda klasifikuje vety do ctyr kategorii. Rozkazovaci - veta konci
   * vykricnikem. Zjistovaci - veta konci otaznikem a neobsahuje tazaci slovo.
   * Doplnovaci - veta konci otaznikem a obsahuje tazaci slovo Oznamovaci -
   * ostatni pripady (nejcasteji veta koncici teckou).
   * 
   * @param listSentences
   *          Vety, ktere maji byt klasifikovany.
   */

  private void categorizeSentences(ArrayList<Sentence> listSentences) {

    for (Sentence sentence : listSentences) {
      /* Veta konci vykricnikem. */
      if (sentence.getContent().charAt(sentence.getContent().length() - 1) == SENTENCE_END_MARKS[0].charAt(0)) {
        sentence.setType(SENT_ROZKAZOVACI);

        /* Veta konci otaznikem. */
      } else if (sentence.getContent().charAt(sentence.getContent().length() - 1) == SENTENCE_END_MARKS[1].charAt(0)) {
        sentence.setType(SENT_ZJISTOVACI);

        /* Veta konci otaznikem a obsahuje tazaci slovo. */
        for (int i = 0; i < QUESTION_WORDS.length; i++) {
          if (sentence.getContent().contains(QUESTION_WORDS[i])) {
            sentence.setType(SENT_DOPLNOVACI);
          }
        }

        /* Ostatni pripady. */
      } else {
        sentence.setType(SENT_OZNAMOVACI);
      }
    }
  }

}
